/*******************************************************************************
 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.core.internal.analysis;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jacoco.core.JaCoCo;
import org.jacoco.core.internal.analysis.filter.Filters;
import org.jacoco.core.internal.analysis.filter.IFilter;
import org.jacoco.core.internal.analysis.filter.IFilterContext;
import org.jacoco.core.internal.diff.ClassInfoDto;
import org.jacoco.core.internal.flow.ClassProbesVisitor;
import org.jacoco.core.internal.flow.MethodProbesVisitor;
import org.jacoco.core.internal.instr.InstrSupport;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * Analyzes the structure of a class.
 */
public class ClassAnalyzer extends ClassProbesVisitor
		implements IFilterContext {

	private final ClassCoverageImpl coverage;
	private final boolean[] probes;
	private final StringPool stringPool;

	private final Set<String> classAnnotations = new HashSet<String>();

	private final Set<String> classAttributes = new HashSet<String>();

	private String sourceDebugExtension;

	private List<ClassInfoDto> classInfos;

	private final IFilter filter;
	
	private boolean oldAnalyzing = false;
	/**
	 * 历史版本指令覆盖率，key为方法签名，value为指令覆盖率Map<AbstractInsnNode-key, Instruction> add by liao
	 * key:Method签名, value为指令覆盖率
	 */
    private Map<String, Map<String, Instruction>> classInstructionsMap;

	/**
	 * Creates a new analyzer that builds coverage data for a class.
	 *
	 * @param coverage
	 *            coverage node for the analyzed class data
	 * @param probes
	 *            execution data for this class or <code>null</code>
	 * @param stringPool
	 *            shared pool to minimize the number of {@link String} instances
	 */
	public ClassAnalyzer(final ClassCoverageImpl coverage,
			final boolean[] probes, final StringPool stringPool) {
		this.coverage = coverage;
		this.probes = probes;
		this.stringPool = stringPool;
		this.filter = Filters.all();
		this.classInstructionsMap = new HashMap<>();
	}

	public ClassAnalyzer(final ClassCoverageImpl coverage,
			final boolean[] probes, final StringPool stringPool,
			List<ClassInfoDto> classInfos, boolean oldAnalyzing, Map<String, Map<String, Instruction>> classInstructionsMap) {
		this.coverage = coverage;
		this.probes = probes;
		this.stringPool = stringPool;
		this.filter = Filters.all();
		this.classInfos = classInfos;
		this.oldAnalyzing = oldAnalyzing;
		this.classInstructionsMap = classInstructionsMap;
		if(null==classInstructionsMap) {
			this.classInstructionsMap = new HashMap<>();
		}
	}

	public List<ClassInfoDto> getClassInfos() {
		return this.classInfos;
	}

	public void setClassInfos(List<ClassInfoDto> classInfos) {
		this.classInfos = classInfos;
	}

	@Override
	public void visit(final int version, final int access, final String name,
			final String signature, final String superName,
			final String[] interfaces) {
		coverage.setSignature(stringPool.get(signature));
		coverage.setSuperName(stringPool.get(superName));
		coverage.setInterfaces(stringPool.get(interfaces));
	}

	@Override
	public AnnotationVisitor visitAnnotation(final String desc,
			final boolean visible) {
		classAnnotations.add(desc);
		return super.visitAnnotation(desc, visible);
	}

	@Override
	public void visitAttribute(final Attribute attribute) {
		classAttributes.add(attribute.type);
	}

	@Override
	public void visitSource(final String source, final String debug) {
		coverage.setSourceFileName(stringPool.get(source));
		sourceDebugExtension = debug;
	}

	@Override
	public MethodProbesVisitor visitMethod(final int access, final String name,
			final String desc, final String signature,
			final String[] exceptions) {
		InstrSupport.assertNotInstrumented(name, coverage.getName());
		// 对象接受了指令的覆盖信息
		final InstructionsBuilder builder = new InstructionsBuilder(probes);
		// 对方法解析完毕后的一个方法，从visitMethod的mv对象调用过来
		return new MethodAnalyzer(builder) {
			// 添加方法覆盖
			@Override
			public void accept(final MethodNode methodNode,
					final MethodVisitor methodVisitor) {
				// 这里遍历每个方法，其中记录了每个方法的指令，最终通过指令的行去映射探针的行
				super.accept(methodNode, methodVisitor);
				// 这里计算源码的覆盖率，builder里面存储类的指令覆盖情况，如果需要做不同版本代码的合并，思路应该是合并builder add by liao
				String methodName = stringPool.get(name);
				if(!oldAnalyzing && !classInstructionsMap.isEmpty()) {
					Map<String, Instruction> oldInst = classInstructionsMap.get(name+JaCoCo.SPLIT_UNIQUE_VAL+desc);
					if(null != oldInst && !oldInst.isEmpty()) {
//						if(methodName.indexOf("one")!=-1 || methodName.indexOf("two")!=-1 || methodName.indexOf("test3")!=-1) {
//							System.out.println(coverage.getName()+", "+methodName);
//							Map<AbstractInsnNode, Instruction> instructions = builder.getInstructions();
//							for (Map.Entry<AbstractInsnNode, Instruction> entry: instructions.entrySet()) {
//								Instruction instruction = entry.getValue();
//								System.out.println(instruction.coveredBranchesToString() );
//							}
//							System.out.println("old");
//							for (Map.Entry<String, Instruction> entry: oldInst.entrySet()) {
//								Instruction instruction = entry.getValue();
//								System.out.println(instruction.coveredBranchesToString() );
//							}
//						}
						Map<AbstractInsnNode, Instruction> instructions = builder.getInstructions();
						for (Map.Entry<AbstractInsnNode, Instruction> entry: instructions.entrySet()) {
							String keyStr = getAbstractInsnNodeUniquekeyStr(entry.getKey());
							Instruction instruction = entry.getValue();
							Instruction other = oldInst.get(keyStr);
							if(null!=other) {
//								Instruction newInst = instruction.merge(other);
								instruction.mergeCoveredBranches(other);
								entry.setValue(instruction);
							}
						}
					}
				}
				addMethodCoverage(stringPool.get(name), stringPool.get(desc),
						stringPool.get(signature), builder, methodNode);
			}
		};
	}

	private void addMethodCoverage(final String name, final String desc,
			final String signature, final InstructionsBuilder icc,
			final MethodNode methodNode) {
		final MethodCoverageCalculator mcc = new MethodCoverageCalculator(
				icc.getInstructions());
		filter.filter(methodNode, this, mcc);

		final MethodCoverageImpl mc = new MethodCoverageImpl(name, desc,
				signature);
		mcc.calculate(mc);

		if (mc.containsCode()) {
			// Only consider methods that actually contain code
			coverage.addMethod(mc);
		}
		if(oldAnalyzing) {
			// 缓存方法覆盖数据 add by liao 
			if (null != icc.getInstructions()
					&& !icc.getInstructions().isEmpty()) {
//				System.out.println(coverage.getName()+"方法名"+name);
				Map<String, Instruction> methodIns = new HashMap<>();
				for (Map.Entry<AbstractInsnNode, Instruction> entry: icc.getInstructions().entrySet()) {
					String keyStr = getAbstractInsnNodeUniquekeyStr(entry.getKey());
					methodIns.put(keyStr, entry.getValue());
//					keyStr = keyStr+" , ";
//					System.out.print(entry.getValue().getLine()+" , ");
//					System.out.print(keyStr);
//					System.out.println(entry.getValue().getInstructionSign());
				}
				classInstructionsMap.put(name+JaCoCo.SPLIT_UNIQUE_VAL+desc, methodIns);
			}
		}
	}

	@Override
	public FieldVisitor visitField(final int access, final String name,
			final String desc, final String signature, final Object value) {
		InstrSupport.assertNotInstrumented(name, coverage.getName());
		return super.visitField(access, name, desc, signature, value);
	}

	@Override
	public void visitTotalProbeCount(final int count) {
		// nothing to do
	}

	// IFilterContext implementation

	public String getClassName() {
		return coverage.getName();
	}

	public String getSuperClassName() {
		return coverage.getSuperName();
	}

	public Set<String> getClassAnnotations() {
		return classAnnotations;
	}

	public Set<String> getClassAttributes() {
		return classAttributes;
	}

	public String getSourceFileName() {
		return coverage.getSourceFileName();
	}

	public String getSourceDebugExtension() {
		return sourceDebugExtension;
	}

	public Map<String, Map<String, Instruction>> getClassInstructionsMap() {
		return classInstructionsMap;
	}
	/**
	 * 获取指令唯一值
	 */
	private String getAbstractInsnNodeUniquekeyStr(AbstractInsnNode insnNode) {
		String keyStr = null;
		if (insnNode instanceof InsnNode) {
			InsnNode insn = (InsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType(); 

		} else if (insnNode instanceof FieldInsnNode) {
			FieldInsnNode insn = (FieldInsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType()+"#"+ insn.owner+"#"+ insn.name+"#"+ insn.desc;
			   
		} else if (insnNode instanceof VarInsnNode) {
			VarInsnNode insn = (VarInsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType()+"#" + insn.var;
			
		} else if (insnNode instanceof MethodInsnNode) {
			MethodInsnNode insn = (MethodInsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType()+"#"+ insn.owner+"#"+ insn.name+"#"+ insn.desc;
			  
		} else if (insnNode instanceof InsnNode) {
			InsnNode insn = (InsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType();
			
		} else if (insnNode instanceof IntInsnNode) {
			IntInsnNode insn = (IntInsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType() +"#"+ insn.operand;
			
		} else if (insnNode instanceof JumpInsnNode) {
			JumpInsnNode insn = (JumpInsnNode)insnNode;
			keyStr = insn.getOpcode()+"#"+insn.getType();
		} else {
			keyStr = insnNode.toString();
		}
		return keyStr.trim();
	}
	
}
